/**********************************************************************************
// D3DGame Source
//
// Creation:	06 Apr 2011
// Updated:		27 Apr 2011
// Compiler:	Visual Studio 2010
//
// Notes:		A D3DGame is a WindowsGame that makes use of Direct3D
//				functions to access the video card aceleration hardware. 
**********************************************************************************/

#include "D3DGame.h"

/**********************************************************************************/

// static member variables
LPDIRECT3DDEVICE9  D3DGame::d3dDev	     = nullptr;			// direct3d device
LPDIRECT3DSURFACE9 D3DGame::backBuffer   = nullptr;			// direct3D back buffer
LPDIRECT3DTEXTURE9 D3DGame::drawingTex	 = nullptr;			// drawing texture
LPD3DXSPRITE	   D3DGame::renderer	 = nullptr;			// sprite renderer
int	  			   D3DGame::videoMemoryPitch = 0;			// bytes in each line of Video Memory
uint *			   D3DGame::videoMemory  = nullptr;			// pointer to Video Memory (32 bytes per pixel mode)
Viewport		   D3DGame::viewport     = {0, 0, 0, 0};	// viewport window rectangle

/**********************************************************************************/

D3DGame::D3DGame() 
{
	fpsMode     = FIXED30FPS;								// default to 30 FPS
	bgColor     = D3DCOLOR_XRGB(0,0,0);						// default background to black color 
	d3d		    = nullptr;									// direct3d object
	
	ZeroMemory(&d3dPP, sizeof(d3dPP));						// clear out d3dPP structure
	ZeroMemory(&lockedTex, sizeof(lockedTex));				// clear out lockedrect structure
	ZeroMemory(&lostDeviceTime,  sizeof(lostDeviceTime));	// time the device was lost
	
	sysFont     = nullptr;									// default engine system font

	viewport.left   = 0;									// viewport defaults to screen size
	viewport.top    = 0;									// viewport defaults to screen size
	viewport.right  = GetWindowWidth()-1.0f;				// viewport defaults to screen size
	viewport.bottom = GetWindowHeight()-1.0f;				// viewport defaults to screen size
}

/**********************************************************************************/

D3DGame::~D3DGame()
{
	if (sysFont)
	{
		delete sysFont;
		sysFont = nullptr;
	}

	// release sprite renderer
	if (renderer)
	{
		renderer->Release();
		renderer = nullptr;
	}

	// release drawing texture
	if (drawingTex)
	{
		drawingTex->Release();
		drawingTex = nullptr;
	}

	// release the direct3D device 
	if (d3dDev)
	{
		d3dDev->Release();
		d3dDev = nullptr;
	}

	// release the direct3D object
	if (d3d)
	{
		d3d->Release();
		d3d = nullptr;
	}
}


/**********************************************************************************/

void D3DGame::LockScreen()
{
	// lock the drawing texture for software rendering
	drawingTex->LockRect(0, &lockedTex, nullptr, D3DLOCK_DISCARD | D3DLOCK_NOSYSLOCK);

	// clear the drawing texture before the next frame
	ZeroMemory(lockedTex.pBits, lockedTex.Pitch * GetWindowHeight());
	
	// set the video memory pointer and the memory pitch

	// 32 bits per pixel screen format
	// -------------------------------
	// The video memory is divided in blocks of 2 bytes (16 bits)
	// Pitch is given in (pixel width * 2 bytes per pixel), i.e
	// it must be divided by 2 to obtain the correct pixel width 
	// of each line of video memory. 
	videoMemoryPitch = lockedTex.Pitch >> 2;
	videoMemory = (UINT *) lockedTex.pBits;
}

/**********************************************************************************/

void D3DGame::UnLockScreen()
{
	// unlock the drawing texture
	drawingTex->UnlockRect(0);

	// create a translate vector
	D3DXVECTOR2 translation(0, 0);

	// create a scale vector
	D3DXVECTOR2 scale(1.0f, 1.0f);
	
	// create tranformation matrix
	D3DXMATRIX mat;
	D3DXMatrixTransformation2D(&mat, nullptr, 0.0f, &scale, nullptr, 0, &translation);
	
	// use the transformations in the draw 
	renderer->SetTransform(&mat);

	// Draw the drawing texture at the position x, y of the backbuffer
	renderer->Draw(
		drawingTex,								// texture
		nullptr,								// source rectangle 
		nullptr,								// center of the texture
		nullptr,								// position of the sprite onscreen
		D3DCOLOR_XRGB(255,255,255));			// WHITE mantains the original colors
}

/**********************************************************************************/

bool D3DGame::Initialize(HINSTANCE appInstance)
{
	// create window
	WindowsGame::Initialize(appInstance);

	// create IDirect3D9 Interface object
	d3d = Direct3DCreate9(D3D_SDK_VERSION);

	// set Direct3D presentation parameters
	d3dPP.BackBufferWidth = GetWindowWidth();				// backbuffer width 
	d3dPP.BackBufferHeight = GetWindowHeight();				// backbuffer height
	d3dPP.BackBufferFormat = D3DFMT_X8R8G8B8;				// backbuffer color format
	d3dPP.BackBufferCount = 1;								// number of backbuffers 
	d3dPP.SwapEffect = D3DSWAPEFFECT_DISCARD;				// buffers swap behaviour
	d3dPP.hDeviceWindow = WindowsGame::GetWindowHandle();	// window handle 
	d3dPP.Windowed = GetWindowMode();						// windowed or fullscreen

	// create Direct3D device
	if FAILED(d3d->CreateDevice(D3DADAPTER_DEFAULT, 
								D3DDEVTYPE_HAL, 
								WindowsGame::GetWindowHandle(),
								D3DCREATE_SOFTWARE_VERTEXPROCESSING,
								&d3dPP, 
								&d3dDev))
		return false;

	// get pointer to the backbuffer
	d3dDev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backBuffer);

	// create drawing texture
	if FAILED(d3dDev->CreateTexture(
		GetWindowWidth(),
		GetWindowHeight(),
		1,
		D3DUSAGE_DYNAMIC,
		D3DFMT_A8R8G8B8,
		D3DPOOL_DEFAULT,
		&drawingTex,
		NULL))
		return false;

	// create sprite renderer
	if FAILED(D3DXCreateSprite(d3dDev, &renderer))
		return false;

	// create tahoma font for engine messages
	sysFont = new Font("Tahoma", 16);

	// return success
	return true;
}

/**********************************************************************************/

bool D3DGame::Reset()
{
	// if the device is not operational 
	// return and try again at a later time
	if (d3dDev->TestCooperativeLevel() != D3DERR_DEVICENOTRESET)
		return false;

	// unload device dependent resources
	UnloadResources();

	// release sprite renderer
	if (renderer)
	{
		renderer->Release();
		renderer = NULL;
	}

	// release drawing texture
	if (drawingTex)
	{
		drawingTex->Release();
		drawingTex = NULL;
	}

	// release backbuffer
	if (backBuffer)
	{
		backBuffer->Release();
		backBuffer = NULL;
	}

	// reset the device
	if FAILED (d3dDev->Reset(&d3dPP))
		return false;

	// get pointer to the backbuffer
	if FAILED(d3dDev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backBuffer))
		return false;

	// re-create the drawing texture
	if FAILED(d3dDev->CreateTexture(
		GetWindowWidth(),
		GetWindowHeight(),
		1,
		D3DUSAGE_DYNAMIC,
		D3DFMT_A8R8G8B8,
		D3DPOOL_DEFAULT,
		&drawingTex,
		NULL))
		return false;

	// create sprite renderer
	if FAILED(D3DXCreateSprite(d3dDev, &renderer))
		return false;

	// reload device dependent resources
	LoadResources();

	// sucessful reset
	return true;
}

/**********************************************************************************/

int D3DGame::Start() 
{
	MSG msg;					// generic windows message
	LARGE_INTEGER startTime;	// time before current frame
	LARGE_INTEGER endTime;		// time after current frame
	LARGE_INTEGER freqTime;		// machine CPU frequency (cycles/second)
	float frameTime = 0.0f;		// time elapsed for the current frame in seconds 
	bool lostDevice = false;	// device is initially operational

	#ifdef _DEBUG
	char text[80];				// text buffer for string output
	#endif
	
	// get machine frequency (cycles/second)
	QueryPerformanceFrequency(&freqTime);

	// initialize game here
	GameInit();

	// check FPS mode to use
	float targetTime;

	switch (fpsMode)
	{
	case VARIABLEFPS: targetTime = 0.00f; break;
	case FIXED60FPS:  targetTime = 16.66f; break;
	case FIXED30FPS:  targetTime = 33.33f; break;
	}

	// set sleep resolution
	timeBeginPeriod(1);

	while(true)
	{
		// get start frame time
		QueryPerformanceCounter(&startTime);

		// test if there is a message in queue, if so get it
		if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
		{ 
			// test if this is a quit
			if (msg.message == WM_QUIT)
				break;
	
			// translate any accelerator keys
			TranslateMessage(&msg);

			// send the message to the window proc
			DispatchMessage(&msg);
		} // end if

		// check device state
		if (lostDevice)
		{
			// if device is lost call reset
			if (Reset())
				lostDevice = false;
		} 
		else
		{
			/**************************************************/
			// Game Main 
			/**************************************************/

			// game logic processing goes here
			GameLogic();
	
			// clear the backbuffer for the next frame
			d3dDev->Clear(0, nullptr, D3DCLEAR_TARGET, bgColor, 1.0f, 0);

			// game drawing goes here
			if SUCCEEDED(d3dDev->BeginScene())
			{
				RenderLock();

				GameDraw();
				
				#ifdef _DEBUG
				// draw frame time information in miliseconds
				// use the transformations in the draw 
				sprintf_s(text, "Frame Time: %06.3f ms", frameTime);
				sysFont->Draw(GetWindowWidth()-170, 10, text);
				#endif

				RenderUnLock();
				
				// stop rendering
				d3dDev->EndScene();
			
				// copy back buffer on the screen
				if ((d3dDev->Present(NULL, NULL, NULL, NULL)) == D3DERR_DEVICELOST)
				{
					lostDevice = true;
					QueryPerformanceCounter(&lostDeviceTime);
				}
			} 
		

			/**************************************************/
			// get end frame time
			QueryPerformanceCounter(&endTime);

			// calculate elapsed frame time in miliseconds
			frameTime = (endTime.QuadPart - startTime.QuadPart) / float(freqTime.QuadPart) * 1000;
			float waitTime = frameTime;
			
			// The Sleep() function puts the thread to sleep but there is no guarantee the game will 
			// continue right after the sleeping time has elapsed. That happens because the OS scheduler
			// controls which threads are fired and when. The solution adopted here is to spin the CPU 
			// wainting for the target time, but I am not sure it is the best solution.

			// lock frame updates 
			while (waitTime < targetTime)
			{
				// use sleep only if there is more than 1 milisecond to the target time
				if (targetTime - waitTime > 1)
					Sleep(1);

				QueryPerformanceCounter(&endTime);
				waitTime = (endTime.QuadPart - startTime.QuadPart) / float(freqTime.QuadPart) * 1000;
			}

			/*******************************************************************************/
		}
       
	} // end while

	// reset sleep resolution
	timeEndPeriod(1);

	// closedown game here
	GameShutdown();

	// return to Windows
	return int(msg.wParam);
}

/**********************************************************************************/
// unload resources when device is lost

void D3DGame::UnloadResources()
{
	// user can override this method
}

/**********************************************************************************/
// reload resources after device is operational	

void D3DGame::LoadResources() 
{
	// user can override this method
}							

/**********************************************************************************/